// import com.alibaba.druid.sql.ast.*;
// import com.alibaba.druid.sql.ast.expr.*;
// import com.alibaba.druid.sql.ast.statement.*;
// import com.alibaba.druid.sql.dialect.mysql.ast.MySqlKey;
// import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey;
// import com.alibaba.druid.sql.dialect.mysql.ast.MySqlUnique;
// import com.alibaba.druid.sql.dialect.mysql.ast.MysqlForeignKey;
// import com.alibaba.druid.sql.dialect.mysql.ast.statement.*;
// import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement.MySqlCreateTableStatement_TableSpaceOption;
// import com.alibaba.druid.sql.parser.*;
// import com.alibaba.druid.util.FnvHash;
// import com.alibaba.druid.util.MySqlUtils;
#include "MySqlCreateTableParser.h"
#include "MySqlExprParser.h"
#include "MySqlSelectParser.h"
#include "../ast/statement/MySqlExtPartition.h"
#include "../ast/statement/MySqlPartitionByKey.h"
#include "../ast/statement/MySqlSubPartitionByKey.h"
#include "../ast/statement/MySqlSubPartitionByValue.h"
#include "../ast/statement/MySqlSubPartitionByList.h"
#include "../ast/statement/MySqlTableIndex.h"
#include "../ast/statement/MySqlCreateTableStatement.h"
#include "../ast/MySqlKey.h"
#include "../ast/MySqlPrimaryKey.h"
#include "../ast/MySqlUnique.h"
#include "../ast/MysqlForeignKey.h"
#include "../../../ast/SQLPartitionByRange.h"
#include "../../../ast/SQLPartitionByHash.h"
#include "../../../ast/SQLPartitionByValue.h"
#include "../../../ast/SQLPartitionByList.h"
#include "../../../ast/SQLSubPartitionByHash.h"
#include "../../../ast/SQLSubPartitionByRange.h"
#include "../../../ast/SQLPartition.h"
#include "../../../ast/statement/SQLTableLike.h"
#include "../../../ast/statement/SQLCreateTableStatement.h"
#include "../../../../utils/FnvHash.h"
#include "../../../../utils/MySqlUtils.h"
#include "../../../../utils/instanceof.h"

MySqlCreateTableParser::MySqlCreateTableParser(string_ptr sql)
    : SQLCreateTableParser(MySqlExprParser_ptr(new MySqlExprParser(sql)))
{
}

MySqlCreateTableParser::MySqlCreateTableParser(SQLExprParser_ptr exprParser)
    : SQLCreateTableParser(exprParser)
{
}

SQLCreateTableStatement_ptr MySqlCreateTableParser::parseCreateTable()
{
  return parseCreateTable(BOOL::TRUE);
}

MySqlExprParser_ptr MySqlCreateTableParser::getExprParser()
{
  return std::dynamic_pointer_cast<MySqlExprParser>(exprParser);
}

MySqlCreateTableStatement_ptr MySqlCreateTableParser::parseCreateTable(BOOL_ptr acceptCreate)
{
  MySqlCreateTableStatement_ptr stmt = MySqlCreateTableStatement_ptr(new MySqlCreateTableStatement());
  if (acceptCreate)
  {
    if (lexer->hasComment() && lexer->isKeepComments())
    {
      stmt->addBeforeComment(lexer->readAndResetComments());
    }
    accept(Token::CREATE);
  }

  if (lexer->identifierEquals(make_string_ptr("TEMPORARY")))
  {
    lexer->nextToken();
    stmt->setType(SQLCreateTableStatement::Type::GLOBAL_TEMPORARY);
  }
  else if (lexer->identifierEquals(make_string_ptr("SHADOW")))
  {
    lexer->nextToken();
    stmt->setType(SQLCreateTableStatement::Type::SHADOW);
  }

  if (lexer->identifierEquals(FnvHash::Constants::DIMENSION))
  {
    lexer->nextToken();
    stmt->setDimension(BOOL::TRUE);
  }

  if (*lexer->token() == Token::HINT)
  {
    this->exprParser->parseHints(stmt->getHints());
  }

  if (lexer->identifierEquals(FnvHash::Constants::EXTERNAL))
  {
    lexer->nextToken();
    stmt->setExternal(BOOL::TRUE);
  }

  accept(Token::TABLE);

  if (*lexer->token() == Token::IF ||
      lexer->identifierEquals(make_string_ptr("IF")))
  {
    lexer->nextToken();
    accept(Token::NOT);
    accept(Token::EXISTS);

    stmt->setIfNotExiists(BOOL::TRUE);
  }

  stmt->setName(this->exprParser->name());

  if (*lexer->token() == Token::LIKE)
  {
    lexer->nextToken();
    SQLName_ptr name = this->exprParser->name();
    stmt->setLike(name);
  }

  if (*lexer->token() == Token::WITH)
  {
    SQLSelect_ptr query = SQLSelect_ptr((new MySqlSelectParser(this->exprParser))->select());
    stmt->setSelect(query);
  }
  else if (*lexer->token() == (Token::LPAREN))
  {
    lexer->nextToken();

    if (*lexer->token() == Token::SELECT)
    {
      SQLSelect_ptr query = SQLSelect_ptr((new MySqlSelectParser(this->exprParser))->select());
      stmt->setSelect(query);
    }
    else
    {
      for (;;)
      {
        SQLColumnDefinition_ptr column = nullptr;

        BOOL_ptr global = BOOL::FALSE;
        if (lexer->identifierEquals(FnvHash::Constants::GLOBAL))
        {
          Lexer_SavePoint_ptr mark = lexer->mark();
          lexer->nextToken();
          if (*lexer->token() == Token::INDEX ||
              *lexer->token() == Token::UNIQUE)
          {
            global = BOOL::TRUE;
          }
          else
          {
            lexer->reset(mark);
          }
        }

        BOOL_ptr local = BOOL::FALSE;
        if (lexer->identifierEquals(FnvHash::Constants::LOCAL))
        {
          Lexer_SavePoint_ptr mark = lexer->mark();
          lexer->nextToken();
          if (*lexer->token() == Token::INDEX ||
              *lexer->token() == Token::KEY ||
              *lexer->token() == Token::UNIQUE)
          {
            local = BOOL::TRUE;
          }
          else if (*lexer->token() == Token::FULLTEXT)
          {
            lexer->nextToken();

            if (*lexer->token() == Token::KEY)
            {
              MySqlKey_ptr fulltextKey = MySqlKey_ptr(new MySqlKey());
              this->exprParser->parseIndex(fulltextKey->getIndexDefinition());
              fulltextKey->setIndexType(make_string_ptr("FULLTEXT"));
              fulltextKey->setParent(stmt);
              stmt->getTableElementList()->push_back(fulltextKey);

              while (*lexer->token() == Token::HINT)
              {
                lexer->nextToken();
              }

              if (*lexer->token() == Token::RPAREN)
              {
                break;
              }
              else if (*lexer->token() == Token::COMMA)
              {
                lexer->nextToken();
                continue;
              }
            }
            else if (*lexer->token() == Token::INDEX)
            {
              MySqlTableIndex_ptr idx = MySqlTableIndex_ptr(new MySqlTableIndex());
              this->exprParser->parseIndex(idx->getIndexDefinition());
              idx->setIndexType(make_string_ptr("FULLTEXT"));
              idx->setParent(stmt);
              stmt->getTableElementList()->push_back(idx);

              if (*lexer->token() == Token::RPAREN)
              {
                break;
              }
              else if (*lexer->token() == Token::COMMA)
              {
                lexer->nextToken();
                continue;
              }
            }
            else if (*lexer->token() == Token::IDENTIFIER &&
                     MySqlUtils::isBuiltinDataType(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))))
            {
              lexer->reset(mark);
            }
            else
            {
              MySqlTableIndex_ptr idx = MySqlTableIndex_ptr(new MySqlTableIndex());
              this->exprParser->parseIndex(idx->getIndexDefinition());
              idx->setIndexType(make_string_ptr("FULLTEXT"));
              idx->setParent(stmt);
              stmt->getTableElementList()->push_back(idx);

              if (*lexer->token() == Token::RPAREN)
              {
                break;
              }
              else if (*lexer->token() == Token::COMMA)
              {
                lexer->nextToken();
                continue;
              }
            }
          }
          else if (lexer->identifierEquals(FnvHash::Constants::SPATIAL))
          {
            lexer->nextToken();
            if (*lexer->token() == Token::INDEX ||
                *lexer->token() == Token::KEY ||
                *lexer->token() != Token::IDENTIFIER ||
                !MySqlUtils::isBuiltinDataType(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))))
            {
              MySqlTableIndex_ptr idx = MySqlTableIndex_ptr(new MySqlTableIndex());
              this->exprParser->parseIndex(idx->getIndexDefinition());
              idx->setIndexType(make_string_ptr("SPATIAL"));
              idx->setParent(stmt);
              stmt->getTableElementList()->push_back(idx);

              if (*lexer->token() == Token::RPAREN)
              {
                break;
              }
              else if (*lexer->token() == Token::COMMA)
              {
                lexer->nextToken();
                continue;
              }
            }
            else
            {
              lexer->reset(mark);
            }
          }
          else
          {
            lexer->reset(mark);
          }
        }

        if (*lexer->token() == Token::FULLTEXT)
        {
          Lexer_SavePoint_ptr mark = lexer->mark();
          lexer->nextToken();

          if (*lexer->token() == Token::KEY)
          {
            MySqlKey_ptr fulltextKey = MySqlKey_ptr(new MySqlKey());
            this->exprParser->parseIndex(fulltextKey->getIndexDefinition());
            fulltextKey->setIndexType(make_string_ptr("FULLTEXT"));
            fulltextKey->setParent(stmt);
            stmt->getTableElementList()->push_back(fulltextKey);

            while (*lexer->token() == Token::HINT)
            {
              lexer->nextToken();
            }

            if (*lexer->token() == Token::RPAREN)
            {
              break;
            }
            else if (*lexer->token() == Token::COMMA)
            {
              lexer->nextToken();
              continue;
            }
          }
          else if (*lexer->token() == Token::INDEX)
          {
            MySqlTableIndex_ptr idx = MySqlTableIndex_ptr(new MySqlTableIndex());
            this->exprParser->parseIndex(idx->getIndexDefinition());
            idx->setIndexType(make_string_ptr("FULLTEXT"));
            idx->setParent(stmt);
            stmt->getTableElementList()->push_back(idx);

            if (*lexer->token() == Token::RPAREN)
            {
              break;
            }
            else if (*lexer->token() == Token::COMMA)
            {
              lexer->nextToken();
              continue;
            }
          }
          else if (*lexer->token() == Token::IDENTIFIER &&
                   MySqlUtils::isBuiltinDataType(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))))
          {
            lexer->reset(mark);
          }
          else
          {
            MySqlTableIndex_ptr idx = MySqlTableIndex_ptr(new MySqlTableIndex());
            this->exprParser->parseIndex(idx->getIndexDefinition());
            idx->setIndexType(make_string_ptr("FULLTEXT"));
            idx->setParent(stmt);
            stmt->getTableElementList()->push_back(idx);

            if (*lexer->token() == Token::RPAREN)
            {
              break;
            }
            else if (*lexer->token() == Token::COMMA)
            {
              lexer->nextToken();
              continue;
            }
          }
        }
        else if (lexer->identifierEquals(FnvHash::Constants::SPATIAL))
        {
          Lexer_SavePoint_ptr mark = lexer->mark();
          lexer->nextToken();
          if (*lexer->token() == Token::INDEX ||
              *lexer->token() == Token::KEY ||
              *lexer->token() != Token::IDENTIFIER ||
              !MySqlUtils::isBuiltinDataType(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))))
          {
            MySqlTableIndex_ptr idx = MySqlTableIndex_ptr(new MySqlTableIndex());
            this->exprParser->parseIndex(idx->getIndexDefinition());
            idx->setIndexType(make_string_ptr("SPATIAL"));
            idx->setParent(stmt);
            stmt->getTableElementList()->push_back(idx);

            if (*lexer->token() == Token::RPAREN)
            {
              break;
            }
            else if (*lexer->token() == Token::COMMA)
            {
              lexer->nextToken();
              continue;
            }
          }
          else
          {
            lexer->reset(mark);
          }
        }

        if (lexer->identifierEquals(FnvHash::Constants::ANN))
        {
          Lexer_SavePoint_ptr mark = lexer->mark();
          lexer->nextToken();
          if (*lexer->token() == Token::INDEX ||
              *lexer->token() == Token::KEY)
          {
            MySqlTableIndex_ptr idx = MySqlTableIndex_ptr(new MySqlTableIndex());
            this->exprParser->parseIndex(idx->getIndexDefinition());
            idx->setIndexType(make_string_ptr("ANN"));
            idx->setParent(stmt);
            stmt->getTableElementList()->push_back(idx);

            if (*lexer->token() == Token::RPAREN)
            {
              break;
            }
            else if (*lexer->token() == Token::COMMA)
            {
              lexer->nextToken();
              continue;
            }
          }
          else
          {
            lexer->reset(mark);
          }
        }
        if (lexer->identifierEquals(FnvHash::Constants::CLUSTERED))
        {
          lexer->nextToken();
          if (*lexer->token() == Token::KEY)
          {
            MySqlKey *clsKey = new MySqlKey();
            this->exprParser->parseIndex(clsKey->getIndexDefinition());
            clsKey->setIndexType(make_string_ptr("CLUSTERED"));
            clsKey->setParent(stmt);
            stmt->getTableElementList()->push_back(MySqlKey_ptr(clsKey));

            if (*lexer->token() == Token::COMMA)
            {
              lexer->nextToken();
              continue;
            }
          }
          else if (*lexer->token() == Token::INDEX)
          {
            MySqlTableIndex_ptr idx = MySqlTableIndex_ptr(new MySqlTableIndex());
            this->exprParser->parseIndex(idx->getIndexDefinition());
            idx->setIndexType(make_string_ptr("CLUSTERED"));
            idx->setParent(stmt);
            stmt->getTableElementList()->push_back(idx);

            if (*lexer->token() == Token::RPAREN)
            {
              break;
            }
            else if (*lexer->token() == Token::COMMA)
            {
              lexer->nextToken();
              continue;
            }
          }
        }
        else if (lexer->identifierEquals(FnvHash::Constants::CLUSTERING))
        {
          lexer->nextToken();
          if (*lexer->token() == Token::KEY)
          {
            MySqlKey_ptr clsKey = MySqlKey_ptr(new MySqlKey());
            this->exprParser->parseIndex(clsKey->getIndexDefinition());
            clsKey->setIndexType(make_string_ptr("CLUSTERING"));
            clsKey->setParent(stmt);
            stmt->getTableElementList()->push_back(clsKey);

            if (*lexer->token() == Token::COMMA)
            {
              lexer->nextToken();
              continue;
            }
          }
          else if (*lexer->token() == Token::INDEX)
          {
            MySqlTableIndex_ptr idx = MySqlTableIndex_ptr(new MySqlTableIndex());
            this->exprParser->parseIndex(idx->getIndexDefinition());
            idx->setIndexType(make_string_ptr("CLUSTERING"));
            idx->setParent(stmt);
            stmt->getTableElementList()->push_back(idx);

            if (*lexer->token() == Token::RPAREN)
            {
              break;
            }
            else if (*lexer->token() == Token::COMMA)
            {
              lexer->nextToken();
              continue;
            }
          }
        }
        else if (*lexer->token() == Token::IDENTIFIER //
                 || *lexer->token() == Token::LITERAL_CHARS)
        {
          column = this->exprParser->parseColumn();
          column->setParent(stmt);
          stmt->getTableElementList()->push_back(column);

          if (lexer->isKeepComments() && lexer->hasComment())
          {    string_list_ptr tmp = std::make_shared<std::list<string_ptr>>();
    for (auto it : *lexer->readAndResetComments())
    {
      tmp->push_back(make_string_ptr(encode_util::UnicodeToUTF8(*it)));
    }
            column->addAfterComment(tmp);
          }
        }
        else if (*lexer->token() == Token::CONSTRAINT //
                 || *lexer->token() == Token::PRIMARY //
                 || *lexer->token() == Token::UNIQUE)
        {
          SQLTableConstraint_ptr constraint = this->parseConstraint();
          constraint->setParent(stmt);

          if (instanceof <SQLTableConstraint, MySqlUnique>(*constraint))
          {
            MySqlUnique_ptr unique = std::dynamic_pointer_cast<MySqlUnique>(constraint);
            if (global)
            {
              unique->setGlobal(BOOL::TRUE);
            }
            if (local)
            {
              unique->setLocal(BOOL::TRUE);
            }
          }

          stmt->getTableElementList()->push_back(constraint);
        }
        else if (*lexer->token() == (Token::INDEX))
        {
          MySqlTableIndex_ptr idx = MySqlTableIndex_ptr(new MySqlTableIndex());
          this->exprParser->parseIndex(idx->getIndexDefinition());

          if (global)
          {
            idx->getIndexDefinition()->setGlobal(BOOL::TRUE);
          }

          if (local)
          {
            idx->getIndexDefinition()->setLocal(BOOL::TRUE);
          }

          idx->setParent(stmt);
          stmt->getTableElementList()->push_back(idx);
        }
        else if (*lexer->token() == (Token::KEY))
        {
          Lexer_SavePoint_ptr savePoint = lexer->mark();
          lexer->nextToken();

          BOOL_ptr isColumn = BOOL::FALSE;
          if (lexer->identifierEquals(FnvHash::Constants::VARCHAR))
          {
            isColumn = BOOL::TRUE;
          }
          lexer->reset(savePoint);

          if (isColumn)
          {
            column = this->exprParser->parseColumn();
            stmt->getTableElementList()->push_back(column);
          }
          else
          {
            stmt->getTableElementList()->push_back(parseConstraint());
          }
        }
        else if (*lexer->token() == (Token::PRIMARY))
        {
          SQLTableConstraint_ptr pk = parseConstraint();
          pk->setParent(stmt);
          stmt->getTableElementList()->push_back(pk);
        }
        else if (*lexer->token() == (Token::FOREIGN))
        {
          SQLForeignKeyConstraint_ptr fk = this->getExprParser()->parseForeignKey();
          fk->setParent(stmt);
          stmt->getTableElementList()->push_back(fk);
        }
        else if (*lexer->token() == Token::CHECK)
        {
          SQLCheck_ptr check = this->exprParser->parseCheck();
          stmt->getTableElementList()->push_back(check);
        }
        else if (*lexer->token() == Token::LIKE)
        {
          lexer->nextToken();
          SQLTableLike_ptr tableLike = SQLTableLike_ptr(new SQLTableLike());
          tableLike->setTable(SQLExprTableSource_ptr(new SQLExprTableSource(this->exprParser->name())));
          tableLike->setParent(stmt);
          stmt->getTableElementList()->push_back(tableLike);

          if (lexer->identifierEquals(FnvHash::Constants::INCLUDING))
          {
            lexer->nextToken();
            acceptIdentifier("PROPERTIES");
            tableLike->setIncludeProperties(BOOL::TRUE);
          }
          else if (lexer->identifierEquals(FnvHash::Constants::EXCLUDING))
          {
            lexer->nextToken();
            acceptIdentifier("PROPERTIES");
            tableLike->setExcludeProperties(BOOL::TRUE);
          }
        }
        else
        {
          column = this->exprParser->parseColumn();
          stmt->getTableElementList()->push_back(column);
        }

        if (*lexer->token() == Token::HINT)
        {
          lexer->nextToken();
        }

        if (*lexer->token() != Token::COMMA)
        {
          break;
        }
        else
        {
          lexer->nextToken();
          if (lexer->isKeepComments() && lexer->hasComment() && column != nullptr)
          {    string_list_ptr tmp = std::make_shared<std::list<string_ptr>>();
    for (auto it : *lexer->readAndResetComments())
    {
      tmp->push_back(make_string_ptr(encode_util::UnicodeToUTF8(*it)));
    }
            column->addAfterComment(tmp);
          }
        }
      }
    }

    if (*lexer->token() == Token::HINT)
    {
      lexer->nextToken();
    }

    accept(Token::RPAREN);

    if (*lexer->token() == Token::HINT &&
        lexer->stringVal()->at(0) == '!')
    {
      lexer->nextToken();
    }
  }

  for (;;)
  {
    if (*lexer->token() == Token::COMMA)
    {
      lexer->nextToken();
    }

    if (lexer->identifierEquals(FnvHash::Constants::ENGINE))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      SQLExpr_ptr expr = nullptr;
      if (*lexer->token() == Token::MERGE)
      {
        expr = SQLIdentifierExpr_ptr(new SQLIdentifierExpr(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))));
        lexer->nextToken();
      }
      else
      {
        expr = this->exprParser->expr();
      }
      stmt->setEngine(expr);
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("PAGE_CHECKSUM")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      SQLExpr_ptr expr = this->exprParser->expr();
      stmt->setPageChecksum(expr);
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("TRANSACTIONAL")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      SQLExpr_ptr expr = this->exprParser->expr();
      stmt->setTransactional(expr);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::BLOCK_SIZE))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      SQLExpr_ptr expr = nullptr;
      if (*lexer->token() == Token::MERGE)
      {
        expr = SQLIdentifierExpr_ptr(new SQLIdentifierExpr(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))));
        lexer->nextToken();
      }
      else
      {
        expr = this->exprParser->integerExpr();
      }
      stmt->addOption(make_string_ptr("BLOCK_SIZE"), expr);
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("BLOCK_FORMAT")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      SQLExpr_ptr expr = this->exprParser->primary();
      stmt->addOption(make_string_ptr("BLOCK_FORMAT"), expr);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::REPLICA_NUM))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      SQLExpr_ptr expr = this->exprParser->integerExpr();
      stmt->addOption(make_string_ptr("REPLICA_NUM"), expr);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::TABLET_SIZE))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      SQLExpr_ptr expr = this->exprParser->integerExpr();
      stmt->addOption(make_string_ptr("TABLET_SIZE"), expr);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::PCTFREE))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      SQLExpr_ptr expr = this->exprParser->integerExpr();
      stmt->addOption(make_string_ptr("PCTFREE"), expr);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::USE_BLOOM_FILTER))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      SQLExpr_ptr expr = this->exprParser->primary();
      stmt->addOption(make_string_ptr("USE_BLOOM_FILTER"), expr);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::AUTO_INCREMENT))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("AUTO_INCREMENT"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("AVG_ROW_LENGTH")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("AVG_ROW_LENGTH"), this->exprParser->expr());
      continue;
    }

    if (*lexer->token() == Token::DEFAULT)
    {
      lexer->nextToken();
      parseTableOptionCharsetOrCollate(stmt);
      continue;
    }

    if (parseTableOptionCharsetOrCollate(stmt))
    {
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::CHECKSUM))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("CHECKSUM"), this->exprParser->expr());
      continue;
    }

    if (*lexer->token() == Token::COMMENT)
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->setComment(this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::CONNECTION))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("CONNECTION"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::DATA))
    {
      lexer->nextToken();
      acceptIdentifier("DIRECTORY");
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("DATA DIRECTORY"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("DELAY_KEY_WRITE")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("DELAY_KEY_WRITE"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("FULLTEXT_DICT")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("FULLTEXT_DICT"), this->exprParser->charExpr());
      continue;
    }

    if (*lexer->token() == Token::INDEX)
    {
      lexer->nextToken();
      acceptIdentifier("DIRECTORY");
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("INDEX DIRECTORY"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("INSERT_METHOD")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("INSERT_METHOD"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("KEY_BLOCK_SIZE")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("KEY_BLOCK_SIZE"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::MAX_ROWS))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("MAX_ROWS"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::MIN_ROWS))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("MIN_ROWS"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::PACK_KEYS))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("PACK_KEYS"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::PASSWORD))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("PASSWORD"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("ROW_FORMAT")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("ROW_FORMAT"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("STATS_AUTO_RECALC")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      stmt->addOption(make_string_ptr("STATS_AUTO_RECALC"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("STATS_PERSISTENT")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      stmt->addOption(make_string_ptr("STATS_PERSISTENT"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("STATS_SAMPLE_PAGES")))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      stmt->addOption(make_string_ptr("STATS_SAMPLE_PAGES"), this->exprParser->expr());
      continue;
    }

    if (*lexer->token() == Token::UNION)
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      accept(Token::LPAREN);
      SQLListExpr_ptr list = SQLListExpr_ptr(new SQLListExpr());
      this->exprParser->exprList(list->getItems(), list);
      stmt->addOption(make_string_ptr("UNION"), list);
      accept(Token::RPAREN);
      continue;
    }

    if (*lexer->token() == Token::TABLESPACE)
    {
      lexer->nextToken();

      MySqlCreateTableStatement_TableSpaceOption_ptr option = MySqlCreateTableStatement_TableSpaceOption_ptr(new MySqlCreateTableStatement_TableSpaceOption());
      option->setName(this->exprParser->name());

      if (lexer->identifierEquals(make_string_ptr("STORAGE")))
      {
        lexer->nextToken();
        option->setStorage(this->exprParser->name());
      }

      stmt->addOption(make_string_ptr("TABLESPACE"), option);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::TABLEGROUP))
    {
      lexer->nextToken();

      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }

      SQLName_ptr tableGroup = this->exprParser->name();
      stmt->setTableGroup(tableGroup);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::TYPE))
    {
      lexer->nextToken();
      accept(Token::EQ);
      stmt->addOption(make_string_ptr("TYPE"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("INDEX_ALL")))
    {
      lexer->nextToken();
      accept(Token::EQ);
      if (*lexer->token() == Token::LITERAL_CHARS)
      {
        if (StringUtils::equalsIgnoreCase(make_string_ptr("Y"), make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))))
        {
          lexer->nextToken();
          stmt->addOption(make_string_ptr("INDEX_ALL"), SQLCharExpr_ptr(new SQLCharExpr(make_string_ptr("Y"))));
        }
        else if (StringUtils::equalsIgnoreCase(make_string_ptr("N"), make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))))
        {
          lexer->nextToken();
          stmt->addOption(make_string_ptr("INDEX_ALL"), SQLCharExpr_ptr(new SQLCharExpr(make_string_ptr("N"))));
        }
        else
        {
          throw new ParserException(make_string_ptr("INDEX_ALL accept parameter ['Y' or 'N'] only."));
        }
      }
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("RT_INDEX_ALL")))
    {
      lexer->nextToken();
      accept(Token::EQ);
      if (*lexer->token() == Token::LITERAL_CHARS)
      {
        if (StringUtils::equalsIgnoreCase(make_string_ptr("Y"), make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))))
        {
          lexer->nextToken();
          stmt->addOption(make_string_ptr("RT_INDEX_ALL"), SQLCharExpr_ptr(new SQLCharExpr(make_string_ptr("Y"))));
        }
        else if (StringUtils::equalsIgnoreCase(make_string_ptr("N"), make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))))
        {
          lexer->nextToken();
          stmt->addOption(make_string_ptr("RT_INDEX_ALL"), SQLCharExpr_ptr(new SQLCharExpr(make_string_ptr("N"))));
        }
        else
        {
          throw new ParserException(make_string_ptr("RT_INDEX_ALL accepts parameter ['Y' or 'N'] only."));
        }
      }

      continue;
    }
    if (lexer->identifierEquals(FnvHash::Constants::ARCHIVE))
    {
      lexer->nextToken();
      accept(Token::BY);
      acceptIdentifier("OSS");
      stmt->setArchiveBy(SQLIdentifierExpr_ptr(new SQLIdentifierExpr(make_string_ptr("OSS"))));
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("STORAGE_TYPE")))
    {
      lexer->nextToken();
      accept(Token::EQ);
      stmt->addOption(make_string_ptr("STORAGE_TYPE"), this->exprParser->charExpr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("STORAGE_POLICY")))
    {
      lexer->nextToken();
      accept(Token::EQ);
      stmt->addOption(make_string_ptr("STORAGE_POLICY"), this->exprParser->charExpr());
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("HOT_PARTITION_COUNT")))
    {
      lexer->nextToken();
      accept(Token::EQ);
      try
      {
        stmt->addOption(make_string_ptr("HOT_PARTITION_COUNT"), this->exprParser->integerExpr());
        // } catch (Exception e) {
      }
      catch (...)
      {
        throw new ParserException(make_string_ptr("only integer number is supported for hot_partition_count"));
      }
      continue;
    }

    if (lexer->identifierEquals(make_string_ptr("TABLE_PROPERTIES")))
    {
      lexer->nextToken();
      accept(Token::EQ);
      stmt->addOption(make_string_ptr("TABLE_PROPERTIES"), exprParser->charExpr());
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::ENCRYPTION))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("ENCRYPTION"), this->exprParser->expr());
      continue;
    }
    else if (lexer->identifierEquals(FnvHash::Constants::COMPRESSION))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
      }
      stmt->addOption(make_string_ptr("COMPRESSION"), this->exprParser->expr());
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::CLUSTERED))
    {
      lexer->nextToken();
      accept(Token::BY);
      accept(Token::LPAREN);
      for (;;)
      {
        SQLSelectOrderByItem_ptr item = this->exprParser->parseSelectOrderByItem();
        stmt->addClusteredByItem(item);
        if (*lexer->token() == Token::COMMA)
        {
          lexer->nextToken();
          continue;
        }
        break;
      }
      accept(Token::RPAREN);
      continue;
    }

    if (*lexer->token() == Token::PARTITION)
    {
      SQLPartitionBy_ptr partitionClause = parsePartitionBy();
      stmt->setPartitioning(partitionClause);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::LOCAL))
    {
      SQLPartitionBy_ptr localPartitionClause = parseLocalPartitionBy();
      stmt->setLocalPartitioning(localPartitionClause);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::BROADCAST))
    {
      lexer->nextToken();
      stmt->setBroadCast(BOOL::TRUE);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::DISTRIBUTE) ||
        lexer->identifierEquals(FnvHash::Constants::DISTRIBUTED))
    {
      lexer->nextToken();
      accept(Token::BY);
      if (lexer->identifierEquals(FnvHash::Constants::HASH))
      {
        lexer->nextToken();
        accept(Token::LPAREN);
        for (;;)
        {
          SQLName_ptr name = this->exprParser->name();
          stmt->getDistributeBy()->push_back(name);
          if (*lexer->token() == Token::COMMA)
          {
            lexer->nextToken();
            continue;
          }
          break;
        }
        accept(Token::RPAREN);
        stmt->setDistributeByType(SQLIdentifierExpr_ptr(new SQLIdentifierExpr(make_string_ptr("HASH"))));
      }
      else if (lexer->identifierEquals(FnvHash::Constants::DUPLICATE))
      {
        lexer->nextToken();
        accept(Token::LPAREN);
        for (;;)
        {
          SQLName_ptr name = this->exprParser->name();
          stmt->getDistributeBy()->push_back(name);
          if (*lexer->token() == Token::COMMA)
          {
            lexer->nextToken();
            continue;
          }
          break;
        }
        accept(Token::RPAREN);
        stmt->setDistributeByType(SQLIdentifierExpr_ptr(new SQLIdentifierExpr(make_string_ptr("DUPLICATE"))));
      }
      else if (lexer->identifierEquals(FnvHash::Constants::BROADCAST))
      {
        lexer->nextToken();
        stmt->setDistributeByType(SQLIdentifierExpr_ptr(new SQLIdentifierExpr(make_string_ptr("BROADCAST"))));
      }
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::DBPARTITION))
    {
      lexer->nextToken();
      accept(Token::BY);
      SQLExpr_ptr dbPartitoinBy = this->exprParser->primary();
      stmt->setDbPartitionBy(dbPartitoinBy);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::DBPARTITIONS))
    {
      lexer->nextToken();
      SQLExpr_ptr dbPartitions = this->exprParser->primary();
      stmt->setDbPartitions(dbPartitions);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::TBPARTITION))
    {
      lexer->nextToken();
      accept(Token::BY);
      SQLExpr_ptr expr = this->exprParser->expr();
      if (lexer->identifierEquals(FnvHash::Constants::STARTWITH))
      {
        lexer->nextToken();
        SQLExpr_ptr start = this->exprParser->primary();
        acceptIdentifier("ENDWITH");
        SQLExpr_ptr end = this->exprParser->primary();
        expr = SQLBetweenExpr_ptr(new SQLBetweenExpr(expr, start, end));
      }
      stmt->setTablePartitionBy(expr);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::TBPARTITIONS))
    {
      lexer->nextToken();
      SQLExpr_ptr tbPartitions = this->exprParser->primary();
      stmt->setTablePartitions(tbPartitions);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::EXTPARTITION))
    {
      lexer->nextToken();
      accept(Token::LPAREN);

      MySqlExtPartition_ptr partitionDef = MySqlExtPartition_ptr(new MySqlExtPartition());

      for (;;)
      {
        MySqlExtPartition_Item_ptr item = MySqlExtPartition_Item_ptr(new MySqlExtPartition_Item());

        if (lexer->identifierEquals(FnvHash::Constants::DBPARTITION))
        {
          lexer->nextToken();
          SQLName_ptr name = this->exprParser->name();
          item->setDbPartition(name);
          accept(Token::BY);
          SQLExpr_ptr value = this->exprParser->primary();
          item->setDbPartitionBy(value);
        }

        if (lexer->identifierEquals(FnvHash::Constants::TBPARTITION))
        {
          lexer->nextToken();
          SQLName_ptr name = this->exprParser->name();
          item->setTbPartition(name);
          accept(Token::BY);
          SQLExpr_ptr value = this->exprParser->primary();
          item->setTbPartitionBy(value);
        }

        item->setParent(partitionDef);
        partitionDef->getItems()->push_back(item);

        if (*lexer->token() == Token::COMMA)
        {
          lexer->nextToken();
          continue;
        }
        else
        {
          break;
        }
      }
      accept(Token::RPAREN);
      stmt->setExPartition(partitionDef);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::OPTIONS))
    {
      lexer->nextToken();
      accept(Token::LPAREN);

      // stmt->putAttribute("ads.options", Boolean.TRUE);
      for (;;)
      {
        string_ptr name = make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()));
        lexer->nextToken();
        accept(Token::EQ);
        SQLExpr_ptr value = this->exprParser->primary();
        stmt->addOption(name, value);
        if (*lexer->token() == Token::COMMA)
        {
          lexer->nextToken();
          continue;
        }
        break;
      }

      accept(Token::RPAREN);
      continue;
    }

    if (lexer->identifierEquals(FnvHash::Constants::STORED))
    {
      lexer->nextToken();
      accept(Token::BY);
      SQLName_ptr name = this->exprParser->name();
      stmt->setStoredBy(name);
    }

    if (*lexer->token() == Token::WITH)
    {
      lexer->nextToken();
      accept(Token::LPAREN);

      for (;;)
      {
        string_ptr name = make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()));
        lexer->nextToken();
        accept(Token::EQ);
        SQLName_ptr value = this->exprParser->name();
        stmt->getWith()->insert(std::pair<string_ptr, SQLName_ptr>(name, value));
        if (*lexer->token() == Token::COMMA)
        {
          lexer->nextToken();
          continue;
        }
        break;
      }

      accept(Token::RPAREN);
      continue;
    }

    if (*lexer->token() == (Token::HINT))
    {
      this->exprParser->parseHints(stmt->getOptionHints());
      continue;
    }

    break;
  }

  if (*lexer->token() == (Token::ON))
  {
    throw new ParserException(make_string_ptr("TODO. " + encode_util::UnicodeToUTF8(lexer->info())));
  }

  if (*lexer->token() == Token::REPLACE)
  {
    lexer->nextToken();
    stmt->setReplace(BOOL::TRUE);
  }
  else if (lexer->identifierEquals(make_string_ptr("IGNORE")))
  {
    lexer->nextToken();
    stmt->setIgnore(BOOL::TRUE);
  }
  else if (lexer->identifierEquals(make_string_ptr("SINGLE")))
  { // for polardb-x
    lexer->nextToken();
    stmt->setSingle(BOOL::TRUE);
  }

  if (*lexer->token() == (Token::AS))
  {
    lexer->nextToken();

    if (*lexer->token() == Token::LPAREN)
    {
      lexer->nextToken();
      SQLSelect_ptr query = SQLSelect_ptr((new MySqlSelectParser(this->exprParser))->select());
      stmt->setSelect(query);
      accept(Token::RPAREN);
    }
  }

  SQLCommentHint_ptr hint = nullptr;
  if (*lexer->token() == Token::HINT)
  {
    hint = this->exprParser->parseHint();
  }

  if (*lexer->token() == (Token::SELECT))
  {
    SQLSelect_ptr query = SQLSelect_ptr((new MySqlSelectParser(this->exprParser))->select());
    if (hint != nullptr)
    {
      query->setHeadHint(hint);
    }
    stmt->setSelect(query);

    if (*lexer->token() == Token::WITH)
    {
      lexer->nextToken();
      if (lexer->identifierEquals(FnvHash::Constants::NO))
      {
        lexer->nextToken();
        acceptIdentifier("DATA");
        stmt->setWithData(BOOL::FALSE);
      }
      else
      {
        acceptIdentifier("DATA");
        stmt->setWithData(BOOL::TRUE);
      }
    }
  }

  while (*lexer->token() == (Token::HINT))
  {
    this->exprParser->parseHints(stmt->getOptionHints());
  }
  return stmt;
}

SQLPartitionBy_ptr MySqlCreateTableParser::parseLocalPartitionBy()
{
  lexer->nextToken();
  accept(Token::PARTITION);
  accept(Token::BY);
  acceptIdentifier("RANGE");

  SQLPartitionByRange_ptr partitionClause = SQLPartitionByRange_ptr(new SQLPartitionByRange());

  accept(Token::LPAREN);
  partitionClause->addColumn(this->exprParser->name());
  accept(Token::RPAREN);

  if (lexer->identifierEquals(FnvHash::Constants::STARTWITH))
  {
    lexer->nextToken();
    partitionClause->setStartWith(exprParser->expr());
  }

  partitionClause->setInterval(getExprParser()->parseInterval());

  if (lexer->identifierEquals(make_string_ptr("EXPIRE")))
  {
    acceptIdentifier("EXPIRE");
    acceptIdentifier("AFTER");
    partitionClause->setExpireAfter(std::dynamic_pointer_cast<SQLIntegerExpr>(exprParser->expr()).get());
  }

  if (lexer->identifierEquals(make_string_ptr("PRE")))
  {
    acceptIdentifier("PRE");
    acceptIdentifier("ALLOCATE");
    partitionClause->setPreAllocate(std::dynamic_pointer_cast<SQLIntegerExpr>(exprParser->expr()).get());
  }

  if (lexer->identifierEquals(make_string_ptr("PIVOTDATE")))
  {
    acceptIdentifier("PIVOTDATE");
    partitionClause->setPivotDateExpr(exprParser->expr());
  }

  if (*lexer->token() == Token::DISABLE)
  {
    lexer->nextToken();
    acceptIdentifier("SCHEDULE");
    partitionClause->setDisableSchedule(BOOL::TRUE);
  }

  return partitionClause;
}

SQLPartitionBy_ptr MySqlCreateTableParser::parsePartitionBy()
{
  lexer->nextToken();
  accept(Token::BY);

  SQLPartitionBy_ptr partitionClause;

  BOOL_ptr linera = BOOL::FALSE;
  if (lexer->identifierEquals(FnvHash::Constants::LINEAR))
  {
    lexer->nextToken();
    linera = BOOL::TRUE;
  }

  if (*lexer->token() == Token::KEY)
  {
    MySqlPartitionByKey_ptr clause = MySqlPartitionByKey_ptr(new MySqlPartitionByKey());
    lexer->nextToken();

    if (linera)
    {
      clause->setLinear(BOOL::TRUE);
    }

    if (lexer->identifierEquals(FnvHash::Constants::ALGORITHM))
    {
      lexer->nextToken();
      accept(Token::EQ);
      clause->setAlgorithm(lexer->integerValue());
      lexer->nextToken();
    }

    accept(Token::LPAREN);
    if (*lexer->token() != Token::RPAREN)
    {
      for (;;)
      {
        clause->addColumn(this->exprParser->name());
        if (*lexer->token() == Token::COMMA)
        {
          lexer->nextToken();
          continue;
        }
        break;
      }
    }
    accept(Token::RPAREN);

    partitionClause = clause;

    partitionClauseRest(clause);
  }
  else if (lexer->identifierEquals(make_string_ptr("HASH")) ||
   lexer->identifierEquals(make_string_ptr("UNI_HASH")))
  {
    SQLPartitionByHash_ptr clause = SQLPartitionByHash_ptr(new SQLPartitionByHash());

    if (lexer->identifierEquals(make_string_ptr("UNI_HASH")))
    {
      clause->setUnique(BOOL::TRUE);
    }

    lexer->nextToken();

    if (linera)
    {
      clause->setLinear(BOOL::TRUE);
    }

    if (*lexer->token() == Token::KEY)
    {
      lexer->nextToken();
      clause->setKey(BOOL::TRUE);
    }

    accept(Token::LPAREN);
    this->exprParser->exprList(clause->getColumns(), clause);
    accept(Token::RPAREN);
    partitionClause = clause;

    partitionClauseRest(clause);
  }
  else if (lexer->identifierEquals(make_string_ptr("RANGE")))
  {
    SQLPartitionByRange_ptr clause = partitionByRange();
    partitionClause = clause;

    partitionClauseRest(clause);
  }
  else if (lexer->identifierEquals(make_string_ptr("VALUE")))
  {
    SQLPartitionByValue_ptr clause = partitionByValue();
    partitionClause = clause;

    partitionClauseRest(clause);
  }
  else if (lexer->identifierEquals(make_string_ptr("LIST")))
  {
    lexer->nextToken();
    SQLPartitionByList_ptr clause = SQLPartitionByList_ptr(new SQLPartitionByList());

    if (*lexer->token() == Token::LPAREN)
    {
      lexer->nextToken();
      clause->addColumn(this->exprParser->expr());
      accept(Token::RPAREN);
    }
    else
    {
      acceptIdentifier("COLUMNS");
      accept(Token::LPAREN);
      for (;;)
      {
        clause->addColumn(this->exprParser->name());
        if (*lexer->token() == Token::COMMA)
        {
          lexer->nextToken();
          continue;
        }
        break;
      }
      accept(Token::RPAREN);
    }
    partitionClause = clause;

    partitionClauseRest(clause);
  }
  else if (*lexer->token() == Token::IDENTIFIER)
  {
    SQLPartitionByRange_ptr clause = partitionByRange();
    partitionClause = clause;

    partitionClauseRest(clause);
  }
  else
  {
    throw new ParserException(make_string_ptr("TODO. " + encode_util::UnicodeToUTF8(lexer->info())));
  }

  if (lexer->identifierEquals(FnvHash::Constants::LIFECYCLE))
  {
    lexer->nextToken();
    partitionClause->setLifecycle(std::dynamic_pointer_cast<SQLIntegerExpr>(exprParser->expr()).get());
  }

  if (*lexer->token() == Token::LPAREN)
  {
    lexer->nextToken();
    for (;;)
    {
      SQLPartition_ptr partitionDef = this->getExprParser()->parsePartition();

      partitionClause->addPartition(partitionDef);

      if (*lexer->token() == Token::COMMA)
      {
        lexer->nextToken();
        continue;
      }
      else
      {
        break;
      }
    }
    accept(Token::RPAREN);
  }
  return partitionClause;
}

SQLPartitionByRange_ptr MySqlCreateTableParser::partitionByRange1()
{
  acceptIdentifier("RANGE");

  SQLPartitionByRange_ptr clause = SQLPartitionByRange_ptr(new SQLPartitionByRange());

  if (*lexer->token() == Token::LPAREN)
  {
    lexer->nextToken();
    clause->addColumn(this->exprParser->expr());
    accept(Token::RPAREN);
  }
  else
  {
    acceptIdentifier("COLUMNS");
    accept(Token::LPAREN);
    for (;;)
    {
      clause->addColumn(this->exprParser->name());
      if (*lexer->token() == Token::COMMA)
      {
        lexer->nextToken();
        continue;
      }
      break;
    }
    accept(Token::RPAREN);
  }
  return clause;
}

SQLPartitionByValue_ptr MySqlCreateTableParser::partitionByValue()
{
  SQLPartitionByValue_ptr clause = SQLPartitionByValue_ptr(new SQLPartitionByValue());
  if (lexer->identifierEquals(FnvHash::Constants::VALUE))
  {
    lexer->nextToken();

    if (*lexer->token() == Token::LPAREN)
    {
      lexer->nextToken();
      clause->addColumn(this->exprParser->expr());
      accept(Token::RPAREN);
    }
  }
  return clause;
}

SQLPartitionByRange_ptr MySqlCreateTableParser::partitionByRange()
{
  SQLPartitionByRange_ptr clause = SQLPartitionByRange_ptr(new SQLPartitionByRange());
  if (lexer->identifierEquals(FnvHash::Constants::RANGE))
  {
    lexer->nextToken();

    if (*lexer->token() == Token::LPAREN)
    {
      lexer->nextToken();
      clause->addColumn(this->exprParser->expr());
      accept(Token::RPAREN);
    }
    else
    {
      acceptIdentifier("COLUMNS");
      accept(Token::LPAREN);
      for (;;)
      {
        clause->addColumn(this->exprParser->name());
        if (*lexer->token() == Token::COMMA)
        {
          lexer->nextToken();
          continue;
        }
        break;
      }
      accept(Token::RPAREN);
    }
  }
  else
  {
    SQLExpr_ptr expr = this->exprParser->expr();
    if (lexer->identifierEquals(FnvHash::Constants::STARTWITH))
    {
      lexer->nextToken();
      SQLExpr_ptr start = this->exprParser->primary();
      acceptIdentifier("ENDWITH");
      SQLExpr_ptr end = this->exprParser->primary();
      expr = SQLBetweenExpr_ptr(new SQLBetweenExpr(expr, start, end));
    }
    clause->setInterval(expr);
  }

  return clause;
}

void MySqlCreateTableParser::partitionClauseRest(SQLPartitionBy_ptr clause)
{
  if (lexer->identifierEquals(FnvHash::Constants::PARTITIONS) ||
   lexer->identifierEquals(FnvHash::Constants::TBPARTITIONS) || 
   lexer->identifierEquals(FnvHash::Constants::DBPARTITIONS))
  {
    lexer->nextToken();
    SQLIntegerExpr_ptr countExpr = this->exprParser->integerExpr();
    clause->setPartitionsCount(countExpr);
  }

  if (lexer->token()->name->c_str() == Token::PARTITION.name->c_str())
  {
    lexer->nextToken();

    if (lexer->identifierEquals(make_string_ptr("NUM")))
    {
      lexer->nextToken();
    }

    clause->setPartitionsCount(this->exprParser->expr());

    // clause->putAttribute("ads.partition", Boolean.TRUE);
  }

  if (lexer->identifierEquals(FnvHash::Constants::LIFECYCLE))
  {
    lexer->nextToken();
    clause->setLifecycle(std::dynamic_pointer_cast<SQLIntegerExpr>(exprParser->expr()).get());
  }

  if (lexer->identifierEquals(FnvHash::Constants::SUBPARTITION))
  {
    lexer->nextToken();
    accept(Token::BY);

    SQLSubPartitionBy_ptr subPartitionByClause = nullptr;

    BOOL_ptr linear = BOOL::FALSE;
    if (lexer->identifierEquals(make_string_ptr("LINEAR")))
    {
      lexer->nextToken();
      linear = BOOL::TRUE;
    }

    if (*lexer->token() == Token::KEY)
    {
      MySqlSubPartitionByKey_ptr subPartitionKey = MySqlSubPartitionByKey_ptr(new MySqlSubPartitionByKey());
      lexer->nextToken();

      if (linear)
      {
        clause->setLinear(BOOL::TRUE);
      }

      if (lexer->identifierEquals(FnvHash::Constants::ALGORITHM))
      {
        lexer->nextToken();
        accept(Token::EQ);
        subPartitionKey->setAlgorithm(lexer->integerValue());
        lexer->nextToken();
      }

      accept(Token::LPAREN);
      for (;;)
      {
        subPartitionKey->addColumn(this->exprParser->name());
        if (*lexer->token() == Token::COMMA)
        {
          lexer->nextToken();
          continue;
        }
        break;
      }
      accept(Token::RPAREN);

      subPartitionByClause = subPartitionKey;
    }
    else if (lexer->identifierEquals(make_string_ptr("VALUE")))
    {
      MySqlSubPartitionByValue_ptr subPartitionByValue = MySqlSubPartitionByValue_ptr(new MySqlSubPartitionByValue());
      lexer->nextToken();
      accept(Token::LPAREN);
      for (;;)
      {
        subPartitionByValue->addColumn(this->exprParser->expr());
        if (*lexer->token() == Token::COMMA)
        {
          lexer->nextToken();
          continue;
        }
        break;
      }
      accept(Token::RPAREN);

      subPartitionByClause = subPartitionByValue;
    }
    else if (lexer->identifierEquals(make_string_ptr("HASH")))
    {
      lexer->nextToken();
      SQLSubPartitionByHash_ptr subPartitionHash = SQLSubPartitionByHash_ptr(new SQLSubPartitionByHash());

      if (linear)
      {
        clause->setLinear(BOOL::TRUE);
      }

      if (*lexer->token() == Token::KEY)
      {
        lexer->nextToken();
        subPartitionHash->setKey(BOOL::TRUE);
      }

      accept(Token::LPAREN);
      subPartitionHash->setExpr(this->exprParser->expr());
      accept(Token::RPAREN);
      subPartitionByClause = subPartitionHash;
    }
    else if (lexer->identifierEquals(make_string_ptr("LIST")))
    {
      lexer->nextToken();
      MySqlSubPartitionByList_ptr subPartitionList = MySqlSubPartitionByList_ptr(new MySqlSubPartitionByList());

      // for ads
      if (*lexer->token() == Token::KEY)
      {
        lexer->nextToken();
        accept(Token::LPAREN);

        for (;;)
        {
          SQLExpr_ptr expr = this->exprParser->expr();

          if (instanceof <SQLExpr, SQLIdentifierExpr>(expr) &&
                             (lexer->identifierEquals(make_string_ptr("bigint")) ||
                              lexer->identifierEquals(make_string_ptr("long"))))
          {
            string_ptr dataType = make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()));
            lexer->nextToken();

            SQLColumnDefinition_ptr column = this->exprParser->createColumnDefinition();
            column->setName(std::dynamic_pointer_cast<SQLIdentifierExpr>(expr));
            column->setDataType(SQLDataTypeImpl_ptr(new SQLDataTypeImpl(dataType)));
            subPartitionList->addColumn(column);

            // subPartitionList->putAttribute("ads.subPartitionList", Boolean.TRUE);
          }

          subPartitionList->addKey(expr);
          if (*lexer->token() == Token::COMMA)
          {
            lexer->nextToken();
            continue;
          }
          break;
        }
        // subPartitionList.putAttribute("ads.subPartitionList", Boolean.TRUE);
        accept(Token::RPAREN);
      }
      else if (*lexer->token() == Token::LPAREN)
      {
        lexer->nextToken();

        SQLExpr_ptr expr;
        if (*lexer->token() == Token::LITERAL_ALIAS)
        {
          expr = SQLIdentifierExpr_ptr(new SQLIdentifierExpr(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))));
          lexer->nextToken();
        }
        else
        {
          expr = this->exprParser->expr();
        }

        if (instanceof <SQLExpr, SQLIdentifierExpr>(expr) &&
                           (lexer->identifierEquals(make_string_ptr("bigint")) ||
                            lexer->identifierEquals(make_string_ptr("long"))))
        {
          string_ptr dataType = make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()));
          lexer->nextToken();

          SQLColumnDefinition_ptr column = this->exprParser->createColumnDefinition();
          column->setName(std::dynamic_pointer_cast<SQLIdentifierExpr>(expr));
          column->setDataType(SQLDataTypeImpl_ptr(new SQLDataTypeImpl(dataType)));
          subPartitionList->addColumn(column);

          // subPartitionList.putAttribute("ads.subPartitionList", Boolean.TRUE);
        }
        else
        {
          subPartitionList->addKey(expr);
        }
        accept(Token::RPAREN);
      }
      else
      {
        acceptIdentifier("COLUMNS");
        accept(Token::LPAREN);
        for (;;)
        {
          subPartitionList->addColumn(this->exprParser->parseColumn());
          if (*lexer->token() == Token::COMMA)
          {
            lexer->nextToken();
            continue;
          }
          break;
        }
        accept(Token::RPAREN);
      }
      subPartitionByClause = subPartitionList;
    }
    else if (lexer->identifierEquals(FnvHash::Constants::RANGE))
    {
      lexer->nextToken();
      SQLSubPartitionByRange_ptr range = SQLSubPartitionByRange_ptr(new SQLSubPartitionByRange());

      accept(Token::LPAREN);
      this->exprParser->exprList(range->getColumns(),range);
      accept(Token::RPAREN);
      subPartitionByClause = range;
    }

    if (lexer->identifierEquals(FnvHash::Constants::SUBPARTITION))
    {
      lexer->nextToken();
      acceptIdentifier("OPTIONS");
      this->exprParser->parseAssignItem(subPartitionByClause->getOptions(), subPartitionByClause);
    }

    if (lexer->identifierEquals(FnvHash::Constants::SUBPARTITIONS))
    {
      lexer->nextToken();
      long intValue = lexer->integerValue();
      SQLNumberExpr_ptr numExpr = SQLNumberExpr_ptr(new SQLNumberExpr(intValue));
      subPartitionByClause->setSubPartitionsCount(numExpr);
      lexer->nextToken();
    }
    else if (lexer->identifierEquals(FnvHash::Constants::PARTITIONS))
    { // ADB
      lexer->nextToken();
      subPartitionByClause->setSubPartitionsCount(std::dynamic_pointer_cast<SQLIntegerExpr>(exprParser->expr()));
      // subPartitionByClause->getAttributes().put("adb.partitons", BOOL::TRUE);
    }

    if (lexer->identifierEquals(FnvHash::Constants::LIFECYCLE))
    {
      lexer->nextToken();
      subPartitionByClause->setLifecycle(std::dynamic_pointer_cast<SQLIntegerExpr>(exprParser->expr()).get());
    }

    if (subPartitionByClause != nullptr)
    {
      subPartitionByClause->setLinear(linear);

      clause->setSubPartitionBy(subPartitionByClause);
    }
  }
}

BOOL_ptr MySqlCreateTableParser::parseTableOptionCharsetOrCollate(MySqlCreateTableStatement_ptr stmt)
{
  if (lexer->identifierEquals(make_string_ptr("CHARACTER")))
  {
    lexer->nextToken();
    accept(Token::SET);
    if (*lexer->token() == Token::EQ)
    {
      lexer->nextToken();
    }
    SQLExpr_ptr charset;
    if (*lexer->token() == Token::IDENTIFIER)
    {
      charset = SQLIdentifierExpr_ptr(new SQLIdentifierExpr(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))));
      lexer->nextToken();
    }
    else if (*lexer->token() == Token::LITERAL_CHARS)
    {
      charset = SQLCharExpr_ptr(new SQLCharExpr(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))));
      lexer->nextToken();
    }
    else
    {
      charset = this->exprParser->primary();
    }
    stmt->addOption(make_string_ptr("CHARACTER SET"), charset);
    return BOOL::TRUE;
  }

  if (lexer->identifierEquals(make_string_ptr("CHARSET")))
  {
    lexer->nextToken();
    if (*lexer->token() == Token::EQ)
    {
      lexer->nextToken();
    }
    SQLExpr_ptr charset;
    if (*lexer->token() == Token::IDENTIFIER)
    {
      charset = SQLIdentifierExpr_ptr(new SQLIdentifierExpr(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))));
      lexer->nextToken();
    }
    else if (*lexer->token() == Token::LITERAL_CHARS)
    {
      charset = SQLCharExpr_ptr(new SQLCharExpr(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()))));
      lexer->nextToken();
    }
    else
    {
      charset = this->exprParser->primary();
    }
    stmt->addOption(make_string_ptr("CHARSET"), charset);
    return BOOL::TRUE;
  }

  if (lexer->identifierEquals(make_string_ptr("COLLATE")))
  {
    lexer->nextToken();
    if (*lexer->token() == Token::EQ)
    {
      lexer->nextToken();
    }
    stmt->addOption(make_string_ptr("COLLATE"), this->exprParser->expr());
    return BOOL::TRUE;
  }

  if (lexer->identifierEquals(make_string_ptr("LOCALITY")))
  {
    lexer->nextToken();
    if (*lexer->token() == Token::EQ)
    {
      lexer->nextToken();
    }
    stmt->addOption(make_string_ptr("LOCALITY"), this->exprParser->expr());
    return BOOL::TRUE;
  }

  return BOOL::FALSE;
}

SQLTableConstraint_ptr MySqlCreateTableParser::parseConstraint()
{
  SQLName_ptr name = nullptr;
  BOOL_ptr hasConstaint = BOOL::FALSE;
  if (*lexer->token() == (Token::CONSTRAINT))
  {
    hasConstaint = BOOL::TRUE;
    lexer->nextToken();
  }

  if (*lexer->token() == Token::IDENTIFIER)
  {
    name = this->exprParser->name();
  }

  SQLTableConstraint_ptr constraint = nullptr;

  if (*lexer->token() == (Token::KEY))
  {
    MySqlKey_ptr key = MySqlKey_ptr(new MySqlKey());
    this->exprParser->parseIndex(key->getIndexDefinition());
    key->setHasConstraint(hasConstaint);

    if (name != nullptr)
    {
      key->setName(name);
    }

    constraint = key;
  }
  else if (*lexer->token() == Token::PRIMARY)
  {
    MySqlPrimaryKey_ptr pk = std::dynamic_pointer_cast<MySqlPrimaryKey>(this->getExprParser()->parsePrimaryKey());
    if (name != nullptr)
    {
      pk->setName(name);
    }
    pk->setHasConstraint(hasConstaint);
    constraint = pk;
  }
  else if (*lexer->token() == Token::UNIQUE)
  {
    MySqlUnique_ptr uk = std::dynamic_pointer_cast<MySqlUnique>(this->getExprParser()->parseUnique());
    // should not use CONSTRAINT [symbol] for index name if index_name already specified
    if (name != nullptr && uk->getName() == nullptr)
    {
      uk->setName(name);
    }

    uk->setHasConstraint(hasConstaint);

    constraint = uk;
  }
  else if (*lexer->token() == Token::FOREIGN)
  {
    MysqlForeignKey_ptr fk = std::dynamic_pointer_cast<MysqlForeignKey>(this->getExprParser()->parseForeignKey());
    fk->setName(name);
    fk->setHasConstraint(hasConstaint);
    constraint = std::dynamic_pointer_cast<SQLTableConstraint>(fk);
  }
  else if (*lexer->token() == Token::CHECK)
  {
    lexer->nextToken();
    SQLCheck_ptr check = SQLCheck_ptr(new SQLCheck());
    check->SQLConstraintImpl::setName(name);
    SQLExpr_ptr expr = this->exprParser->primary();
    check->setExpr(expr);
    constraint = std::dynamic_pointer_cast<SQLTableConstraint>(check);

    BOOL_ptr enforce = BOOL::TRUE;
    if (Token::NOT == *lexer->token())
    {
      enforce = BOOL::FALSE;
      lexer->nextToken();
    }
    if (StringUtils::equalsIgnoreCase(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal())), make_string_ptr("ENFORCED")))
    {
      check->setEnforced(enforce);
      lexer->nextToken();
    }
    if (*lexer->token() == Token::HINT)
    {
      string_ptr hintText = make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()));
      if (hintText != nullptr)
      {
        // hintText = hintText.trim();
        hintText = StringUtils::Trim(hintText);
      }

      if (StringUtils::startWith(hintText, make_string_ptr("!")))
      {
        if (StringUtils::endWith(hintText, make_string_ptr("NOT ENFORCED")))
        {
          check->setEnforced(BOOL::FALSE);
        }
        else if (StringUtils::endWith(hintText, make_string_ptr(" ENFORCED")))
        {
          check->setEnforced(BOOL::TRUE);
        }
        lexer->nextToken();
      }
    }
  }

  if (constraint != nullptr)
  {
    if (*lexer->token() == Token::COMMENT)
    {
      lexer->nextToken();
      SQLExpr_ptr comment = this->exprParser->primary();
      constraint->setComment(comment);
    }

    return constraint;
  }

  throw new ParserException(make_string_ptr("TODO. " + encode_util::UnicodeToUTF8(lexer->info())));
}
